package com.jumper.study.admin.service.impl;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.jumper.study.admin.bo.AdminUserDetails;
import com.jumper.study.admin.dto.req.LoginReq;
import com.jumper.study.admin.dto.resp.ChartData;
import com.jumper.study.admin.dto.resp.UserInfo;
import com.jumper.study.admin.service.AccountService;
import com.jumper.study.admin.service.AdminService;
import com.jumper.study.common.api.ResultCode;
import com.jumper.study.common.exception.Asserts;
import com.jumper.study.common.util.JwtTokenUtil;
import com.jumper.study.db.dao.AccountLoginLogMapper;
import com.jumper.study.db.dao.AccountMapper;
import com.jumper.study.db.dto.DateCountDto;
import com.jumper.study.db.model.Account;
import com.jumper.study.db.model.AccountLoginLog;
import com.jumper.study.db.model.Admin;
import com.jumper.study.db.model.Api;

import cn.hutool.core.util.StrUtil;

@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService, UserDetailsService {
    private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private AdminService adminService;

    @Autowired
    private AccountLoginLogMapper loginMapper;

    @Override
    public UserInfo login(LoginReq req) {
        UserInfo info = null;
        try {
            AdminUserDetails userDetails = (AdminUserDetails)loadUserByUsername(req.getUsername());
            if (!passwordEncoder.matches(req.getPassword(), userDetails.getPassword())) {
                //LOGGER.info("match:{}", userDetails.getPassword());
                Asserts.fail(ResultCode.VALIDATE_FAILED.getCode(), "用户名或密码错误");
            }
            if (!userDetails.isEnabled()) {
                Asserts.fail(ResultCode.UNAUTHORIZED.getCode(), "帐号已禁用");
            }
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            info = new UserInfo();
            //生成token
            info.setUsername(userDetails.getUsername())
                .setName(userDetails.getAccount().getName())
                .setAvatar(userDetails.getAccount().getAvatar())
                .setToken(jwtTokenUtil.generateToken(userDetails))
                .setExpireTime(jwtTokenUtil.getExpiredDateFromToken(info.getToken()).getTime());
            
            //添加登录日志
            insertLoginLog(userDetails.getAccount(), userDetails.getAdminUser(), req);
        } catch(AuthenticationException e) {
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        return info;
    }

    //添加登录日志
    private void insertLoginLog(Account account, Admin admin, LoginReq req) {
        AccountLoginLog log = new AccountLoginLog();
        log.setAccountId(account.getId());
        log.setIp(req.getIp());
        log.setAgent(req.getAgent());
        log.setIsAdmin(admin != null ? 1 : 0);
        loginMapper.insert(log);
    }

    @Override
    public UserDetails loadUserByUsername(String username) {
        //不能直接调用，需要用动态代理否则注解不生效
        Account account = ((AccountService)AopContext.currentProxy()).getByUsername(username);
        if (account != null) {
            Admin adminUser = adminService.getByAccountId(account.getId());
            //todo 读取权限加入
            List<Api> apiList = new ArrayList<Api>();
            return new AdminUserDetails(account, adminUser, apiList);
        }
        throw new UsernameNotFoundException("用户名或密码错误");
    }
    
    @Override
    @Cacheable(value="account_user", key="#username", unless="#result == null")
    public Account getByUsername(String username) {
        LOGGER.info("account service get username:{}", username);
        LambdaQueryWrapper<Account> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(Account::getUsername, username);
        List<Account> accounts = getBaseMapper().selectList(wrapper);
        if (accounts != null && accounts.size() > 0) {
            return accounts.get(0);
        }
        return null;
    }

    @Override
    public boolean save(Account entity) {
        entity.setPassword(passwordEncoder.encode(entity.getPassword()));
        return super.save(entity);
    }

    @Override
    @CacheEvict(value="account_user", key="#entity.id")
    public boolean updateById(Account entity) {
        if (!StrUtil.isEmpty(entity.getPassword())) {
            entity.setPassword(passwordEncoder.encode(entity.getPassword()));
        }
        return super.updateById(entity);
    }

    @Override
    @Caching(evict={
        @CacheEvict(value="account_user", key="#entity.username"),
        @CacheEvict(value="admin_user", key="#entity.id")
    })
    public boolean removeById(Account entity) {
        return super.removeById(entity);
    }

    @Override
    @CacheEvict(value="account_user", key="#username")
    public Boolean resetPasswordById(String username, String password) {
        LambdaUpdateWrapper<Account> wrapper = Wrappers.lambdaUpdate();
        wrapper.set(Account::getPassword, passwordEncoder.encode(password));
        wrapper.eq(Account::getUsername, username);
        Integer ret = getBaseMapper().update(wrapper);
        return SqlHelper.retBool(ret);
    }

    @Override
    @CacheEvict(value="account_user", key="#entity.username")
    public Boolean updatePasswordById(Account entity, String oldPassword, String newPassword) {
        if (!passwordEncoder.matches(oldPassword, entity.getPassword())) {
            Asserts.fail(ResultCode.VALIDATE_FAILED.getCode(), "原密码错误");
        }
        entity.setPassword(newPassword);
        boolean result = updateById(entity);
        return result;
    }

    @Override
    public Long registerCount(LocalDateTime start, LocalDateTime end) {
        LambdaQueryWrapper<Account> query = Wrappers.lambdaQuery();
        if (start != null) {
            query.ge(Account::getCreatedAt, start);
        }
        if (end != null) {
            query.le(Account::getCreatedAt, end);   
        }
        return getBaseMapper().selectCount(query);
    }

    @Override
    public Long loginCount(LocalDateTime start, LocalDateTime end) {
        LambdaQueryWrapper<AccountLoginLog> query = Wrappers.lambdaQuery();
        if (start != null) {
            query.ge(AccountLoginLog::getCreatedAt, start);
        }
        if (end != null) {
            query.le(AccountLoginLog::getCreatedAt, end);
        }
        return loginMapper.selectCount(query);
    }

	@Override
	public ChartData loginDatas(LocalDate date, int days) {
		LocalDate start = date.plusDays(-days);
        Map<String, Long> registerMap = getBaseMapper().selectRegisterCount(
            LocalDateTime.of(start, LocalTime.of(0, 0)),
            LocalDateTime.of(date, LocalTime.of(23, 59, 59))
        ).stream().collect(Collectors.toMap(DateCountDto::getDate, DateCountDto::getCount));
        Map<String, Long> loginMap = loginMapper.selectLoginCount(
            LocalDateTime.of(start, LocalTime.of(0, 0)),
            LocalDateTime.of(date, LocalTime.of(23, 59, 59))
        ).stream().collect(Collectors.toMap(DateCountDto::getDate, DateCountDto::getCount));

        List<String> categories = new ArrayList<>(days);
        List<Long> registerData = new ArrayList<>(days);
        List<Long> loginData = new ArrayList<>(days);

        for(LocalDate d = start; d.isBefore(date); d = d.plusDays(1)) {
            String dateStr = d.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            categories.add(dateStr);
            if (registerMap.containsKey(dateStr)) {
                registerData.add(registerMap.get(dateStr));
            } else {
                registerData.add(0L);
            }
            if (loginMap.containsKey(dateStr)) {
                loginData.add(loginMap.get(dateStr));   
            } else {
                loginData.add(0L);
            }
        }

        Map<String, List<Long>> data = new HashMap<>();
        data.put("注册人数", registerData);
        data.put("登录人数", loginData);
        ChartData result = new ChartData();
        result.setCategories(categories);
        result.setDatas(data);

        return result;
	}
}
